#![allow(unused_variables)]
#![allow(non_snake_case)]

use anyhow::{anyhow, Result};
use std::ffi::c_void;
use std::os::raw::c_int;
use windows_sys::Win32::Foundation::*;
use windows_sys::Win32::System::Diagnostics::Debug::OutputDebugStringW;
use windows_sys::Win32::System::LibraryLoader::{DisableThreadLibraryCalls, GetModuleHandleW};
use windows_sys::Win32::System::Memory::{VirtualProtect, PAGE_EXECUTE_READWRITE};
use windows_sys::Win32::System::ProcessStatus::*;
use windows_sys::Win32::System::SystemServices::{
    DLL_PROCESS_ATTACH, DLL_PROCESS_DETACH, DLL_THREAD_ATTACH, DLL_THREAD_DETACH,
};
use windows_sys::Win32::System::Threading::*;
use windows_sys::Win32::UI::Input::KeyboardAndMouse::*;
use windows_sys::Win32::UI::WindowsAndMessaging::*;

fn debug_string(str: String) {
    use std::os::windows::ffi::OsStrExt;
    let wide_message: Vec<u16> = std::ffi::OsStr::new(&str)
        .encode_wide()
        .chain(std::iter::once(0))
        .collect();
    unsafe {
        OutputDebugStringW(wide_message.as_ptr());
    }
}
fn search_json(
    begin_mark: &[u8],
    end_mark: &[u8],
    begin_char: &[u8],
    end_char: &[u8],
) -> Option<(*const u8, *const u8)> {
    unsafe {
        let hmain = GetModuleHandleW(std::ptr::null());
        let mut module_info: MODULEINFO = std::mem::zeroed();
        if GetModuleInformation(
            GetCurrentProcess(),
            hmain,
            &mut module_info as *mut MODULEINFO,
            std::mem::size_of::<MODULEINFO>() as u32,
        ) != 0
        {
            use memchr::memmem;
            let range: &mut [u8] = std::slice::from_raw_parts_mut(
                module_info.lpBaseOfDll as *mut _,
                module_info.SizeOfImage as usize,
            );
            // 定位到vim.json的开始地址
            let mut vimjson_begin = std::ptr::null();
            let mut vimjson_end = std::ptr::null();
            if let Some(pos) = memmem::find(range, begin_mark) {
                let range1: &[u8] =
                    std::slice::from_raw_parts(module_info.lpBaseOfDll as *const _, pos);
                if let Some(p) = memmem::rfind(range1, begin_char) {
                    vimjson_begin = (module_info.lpBaseOfDll as *const u8).add(p);
                }

                if let Some(pos) = memmem::rfind(range, end_mark) {
                    let range: &[u8] = {
                        std::slice::from_raw_parts(
                            (module_info.lpBaseOfDll as *const u8).add(pos + end_mark.len()),
                            range.len() - pos - end_mark.len() - 1,
                        )
                    };
                    if let Some(p) = memmem::find(range, end_char) {
                        vimjson_end =
                            (module_info.lpBaseOfDll as *const u8).add(pos + end_mark.len() + p);
                    }
                }
            }
            if !vimjson_begin.is_null() && !vimjson_end.is_null() {
                return Some((vimjson_begin, vimjson_end));
            }
        }
    }
    None
}
fn read_json(name: &str) -> Result<String> {
    if let Some(appdata_path) = std::env::var_os("APPDATA") {
        let mut path = appdata_path.to_str().unwrap_or("".into()).to_owned();
        path = path + "\\zed\\";
        path += name;
        let content = std::fs::read_to_string(&path)?;
        if !content.is_empty() {
            return Ok(content);
        }
    }
    Err(anyhow!("read error"))
}
fn patch_json(content: &str, begin: *const u8, end: *const u8) {
    let src = content.as_bytes();
    if src.len() > (end as usize - begin as usize + 1) {
        debug_string(format!(
            "[zedl]patch size checked failed! begin:{:p}, end:{:p}",
            begin, end
        ));
        return;
    }
    unsafe {
        let mut old_protect: u32 = 0;
        let result = VirtualProtect(
            begin as *const _,
            end.offset_from(begin) as usize + 1,
            PAGE_EXECUTE_READWRITE,
            &mut old_protect as *mut u32,
        );
        let range: &mut [u8] =
            std::slice::from_raw_parts_mut(begin as *mut _, end.offset_from(begin) as usize + 1);
        if range.len() > src.len() {
            range.fill(b' '); // 先全部填充空格
            let copy_length = std::cmp::min(range.len(), src.len()); // 必须长度一样
            range[..copy_length].copy_from_slice(src);
        }
        VirtualProtect(
            begin as *const _,
            end.offset_from(begin) as usize + 1,
            old_protect,
            &mut old_protect as *mut u32,
        );
    }
}
// zed把vim的键绑定vim.json写入到资源里去了，直接替换可以间接实现viewmode
fn patch_vim_keymap() {
    if let Ok(json_text) = read_json("patch_vim.json") {
        if let Some((json_begin, json_end)) = search_json(
            b"VimControl && !menu",
            b"\"g g\": \"menu::SelectFirst\"",
            b"[",
            b"]",
        ) {
            debug_string(format!(
                "[zedl]vimjson begin:{:p}, end:{:p}",
                json_begin, json_end
            ));
            patch_json(&json_text, json_begin, json_end);
        }
    }
}

fn patch_default_keymap() {
    if let Ok(json_text) = read_json("patch_default.json") {
        if let Some((json_begin, json_end)) = search_json(
            b"// Standard Linux bindings",
            b"\"ctrl-shift-v\": \"terminal::Paste\"",
            b"[",
            b"]",
        ) {
            debug_string(format!(
                "[zedl]default begin:{:p}, end:{:p}",
                json_begin, json_end
            ));
            patch_json(&json_text, json_begin, json_end);
        }
    }
}

fn patch_emacs_keymap() {
    if let Ok(json_text) = read_json("patch_emacs.json") {
        if let Some((json_begin, json_end)) = search_json(
            b"\"ctrl-g\": \"editor::Cancel\"",
            b"\"ctrl-g\": \"buffer_search::Dismiss\"",
            b"[",
            b"]",
        ) {
            debug_string(format!(
                "[zedl]emacs begin:{:p}, end:{:p}",
                json_begin, json_end
            ));
            patch_json(&json_text, json_begin, json_end);
        }
    }
}

fn patch_default_settings() {
    if let Ok(json_text) = read_json("patch_settings.json") {
        if let Some((json_begin, json_end)) = search_json(
            b"The name of the Zed theme to use for the UI",
            b"\"context_servers\": {}",
            b"{",
            b"}",
        ) {
            debug_string(format!(
                "[zedl]settings begin:{:p}, end:{:p}",
                json_begin, json_end
            ));
            patch_json(&json_text, json_begin, json_end);
        }
    }
}

struct EnumData {
    pid: u32,
    ret: Vec<HWND>,
}
fn enum_windows_item(data: &mut EnumData, hwnd: HWND) -> BOOL {
    let mut p: u32 = 0;
    if unsafe { GetWindowThreadProcessId(hwnd, &mut p as *mut _) != 0 } && p == data.pid {
        unsafe {
            let mut buf: [u8; MAX_PATH as usize] = std::mem::zeroed();
            let len = GetClassNameA(hwnd, &mut buf as *mut _ as *mut _, MAX_PATH as i32);
            if len == 11 && &buf[0..11] == b"Zed::Window" {
                data.ret.push(hwnd);
            }
        }
    }
    1
}
unsafe extern "system" fn enum_callback(win_hwnd: HWND, arg: LPARAM) -> BOOL {
    let pthis = arg as *mut EnumData;
    enum_windows_item(&mut *pthis, win_hwnd)
}

fn get_current_process_wnd() -> Vec<HWND> {
    let mut data = EnumData {
        pid: unsafe { GetCurrentProcessId() },
        ret: Vec::new(),
    };
    unsafe {
        EnumWindows(Some(enum_callback), &mut data as *mut _ as LPARAM);
    }
    data.ret
}
static mut ORG_WINPROC: isize = 0;
extern "system" fn window_proc(hwnd: HWND, msg: u32, wparam: WPARAM, lparam: LPARAM) -> LRESULT {
    unsafe {
        // F8按下不是WM_KEYDOWN
        if msg == WM_SYSKEYDOWN {
            // debug_string(format!(
            //     "[zedl]WM_SYSKEYDOWN wparam:{}, lparam:{}",
            //     wparam, lparam
            // ));
            if wparam == VK_F8.into() && (GetAsyncKeyState(VK_MENU.into()) as u16 & 0x8000 != 0) {
                //debug_string(format!("invoke alt+f8"));
                // forward to alt-i
                PostMessageW(hwnd, WM_KEYDOWN, VK_I.into(), 0);
                PostMessageW(hwnd, WM_KEYUP, VK_I.into(), 0);
                return 0;
            }
        }
        return CallWindowProcW(
            Some(std::mem::transmute(ORG_WINPROC)),
            hwnd,
            msg,
            wparam,
            lparam,
        );
    }
}
// hook zed的主窗口，zed无法检测到alt+控制键，如alt+f8
fn hook_mainwindow() {
    std::thread::spawn(|| {
        std::thread::sleep(std::time::Duration::from_millis(100));
        let mut wnds = get_current_process_wnd();
        let mut try_count = 0;
        while wnds.is_empty() && try_count < 20 {
            std::thread::sleep(std::time::Duration::from_millis(100));
            wnds = get_current_process_wnd();
            try_count += 1;
        }
        if wnds.is_empty() {
            return;
        }
        debug_string(format!("[zedl]found zed window:{:?}", wnds));
        unsafe {
            let result = SetWindowLongPtrW(wnds[0], GWLP_WNDPROC, window_proc as isize);
            if result == 0 && GetLastError() != ERROR_SUCCESS {
                debug_string("SetWindowLongPtrW failed!".to_owned());
                return;
            }
            ORG_WINPROC = result;
        }
    });
}

#[no_mangle]
pub extern "system" fn DllMain(
    hinstDLL: *mut c_void,
    fdwReason: u32,
    _lpReserved: *mut c_void,
) -> c_int {
    match fdwReason {
        DLL_PROCESS_ATTACH => {
            unsafe {
                DisableThreadLibraryCalls(hinstDLL);
                patch_default_settings();
                patch_default_keymap();
                patch_emacs_keymap();
                patch_vim_keymap();
                hook_mainwindow();
            }
            1
        }
        DLL_PROCESS_DETACH => 1,
        DLL_THREAD_ATTACH => 1,
        DLL_THREAD_DETACH => 1,
        _ => 1,
    }
}

#[no_mangle]
pub extern "system" fn dummy() {}
